/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file resource_manager.hpp
 * @author TooOld2Rock'nRoll
 * @date 2024/01/16
 * @brief A resource manager for the ArcadeFighter Library.
 * Thread safe singleton according to Game Programming Patterns.
 *
 * @see: https://gameprogrammingpatterns.com/singleton.html
 *
 * @todo make a Sprite Pool https://gameprogrammingpatterns.com/object-pool.html.
 * @todo save stuff for the user that we don't care for the type (it will be a problem for delete! a callback may be necessary).
 */
#ifndef _RESOURCE_MANAGER_HPP_
#define _RESOURCE_MANAGER_HPP_

/*---- Includes ----*/
#include <map>

#include "graphics/shader.hpp"
#include "graphics/texture.hpp"
#include "graphics/font.hpp"
#include "camera2D.hpp"
#include "audio/audio.hpp"


/*---- Typedefs ----*/
///A more elegant name to keep track of Resource Domain IDs.
typedef unsigned DomainID_t;


/*---- Class Declaration ----*/
/**
 * @brief A simple manager that creates, stores and manage a variety of resources.
 *
 * The main idea of a resource manager is to avoid unnecessary duplication of resources during the lifetime of the game.
 *    Different classes and methods may access a centralized hub that guaranties a specific resource will be correctly
 *    created/loaded only once and its instance shared among anyone that needs it, being deleted after released by all.<br>
 * It is also a way to diminish memory fragmentation, since less memory will be created and deleted repeatedly for
 *    resources that can be reused/recycled between repetitive procedures throughout a game.<br>
 *
 * Trying to predict how programmers prefer to handle their memory use is...difficult. So we tried to offer a balanced
 *    solution that covers most needs without being too restrictive and still cover the basics of a functional resource
 *    manager, organized the resources in what we are calling Domains.<br>
 * A domain can be anything, generally a context like one for each of your levels or maybe specific for different
 *    resource types, but whatever you choose, those resources will exist until you drop that domain, meaning you
 *    don't need those anymore and are giving back its references to be freed, if no one else is using it.<br>
 * Domains may share resources and can ask as many times as it needs for a specific resource without using additional
 *    memory, so remember that resources may already have been initialized somewhere else and will always be valid until
 *    you drop it.<br>
 *
 * Resources are stored using unique keys, it varies from type to type and may be their file path or a name, which will
 *    be used to retrieve its reference on subsequent calls to get.<br>
 * There is one special domain called <b>global</b> that may be used to store resources that should be initialized
 *    early or be shared among many domains and be valid till the program exits. A small game may even use this domain
 *    exclusively, but for most cases it would be advisable to compartimentalize your game in a more logical fashion of
 *    your choosing.<br>
 *
 * The following code illustrates how to create a domain and load a Texture:
 * \code
 *      ResourceManager instance = ResourceManager::instance ();
 *      DomainID_t id = instance.createNewDomain ();
 *      Texture *texture = instance.getTexture (id, "res/the_texture.bmp");
 *
 *      //Don't forget to drop the domain when no longer pertinent!
 * \endcode
 */
class ResourceManager
{
    public:
        ///A place to store and access information pertinent to the library and game running.
        struct global_status
        {
            unsigned u_window_width = 0; ///<The window width in pixels.
            unsigned u_window_height = 0;///<The window height in pixels.

            /* add other references that may be needed globally */

        };//end global_status

    private:
        //the list of resources the class can handle
        //each type should have at least one get method and a entry in the ResourceManager::_eraseResource() method.
        enum resource_type
        {
            GLOBAL_STATUS = 0, //information regarding the general state of program
            SHADER,     //it's a Shader
            CAMERA,     //it's a Camera view
            TEXTURE,    //it's a Texture resource
            FONT,       //it's a Font/Texture resource
            MUSIC,      //it's a SDL_Mixer music
            SOUND_EFFECT,//It's a SDL_Mixer Chunk
            _type_max   //just to keep track of types count
        };//end enum resource_type

        //to keep track of what is stored and if its context is valid.
        struct resource_slot
        {
            ///@todo Make this atomic!
            volatile unsigned short u_count = 0; //how many domains are using this resource
            resource_type _type = resource_type::_type_max; //what kind of resource this is
            void *vp_package = nullptr; //the actual resource
        };//end resource_slot

        //return a unique identification for every new domain requested
        std::atomic<DomainID_t> _last_generated_id;

        //There will only be one of these and anyone can access!
        ResourceManager::global_status g_status;

        //list of registered domains and its resources
        std::multimap<DomainID_t, std::string> map_domains;

        //list of loaded resources
        std::unordered_map<std::string, resource_slot> map_resources;

        //hide the constructor to make the singleton
        ResourceManager ();
        //delete copy and move constructors and assign operators to make singleton safe
        ResourceManager (ResourceManager const&) = delete;            //Copy construct
        ResourceManager (ResourceManager &&) = delete;                //Move construct
        ResourceManager& operator= (ResourceManager const&) = delete; //Copy assign
        ResourceManager& operator= (ResourceManager &&) = delete;     //Move assign

        //checks if a resource domain already has a reference to a specific resource
        bool _resourceForDomainExist (DomainID_t id, const std::string &key) const;
        //the method that actually loads something
        void* _getResource (DomainID_t id, const char *key, resource_type type);
        //deletes the resource package
        void _eraseResource (const std::string &key);


    protected:


    public:
        ///Resource Domain ID reserved for global resources that will only be released when the program exits.
        const static DomainID_t global_domain_id = 0;

        static ResourceManager& instance ();
        ~ResourceManager ();

        DomainID_t createNewDomain ();
        void dropDomain (DomainID_t id);

        /** @brief Access the global status structure, that holds information about the overall state of the running program. */
        ResourceManager::global_status& getGlobalStatus () { return this->g_status; }

        Shader* getBasicShader (DomainID_t id);
        Shader* getShader (DomainID_t id, const char *program_name);

        Camera2D* getDefaultCamera (DomainID_t id);

        Texture* getTexture (DomainID_t id, const char *file_path);
        Font* getFont (DomainID_t id, const char *file_path, unsigned glyph_height);

        UndecodedFileSource* getMusic (DomainID_t id, const char *file_path);
        DecodedFileSource* getSoundEffect (DomainID_t id, const char *file_path);
};//END ResourceManager

#endif //_RESOURCE_MANAGER_HPP_

